package com.soup.common.module.sys.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.Feature;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.soup.common.constant.CommonConstant;
import com.soup.common.constant.LoginContext;
import com.soup.common.entity.ErrorCode;
import com.soup.common.entity.MenuTreeNode;
import com.soup.common.entity.QueryParam;
import com.soup.common.service.BaseServiceImpl;
import com.soup.common.service.CacheService;
import com.soup.common.exception.sys.MenuOperationException;
import com.soup.common.module.sys.data.MenuRequest;
import com.soup.common.module.sys.data.MenuResponse;
import com.soup.common.module.sys.data.MethodMenuResponse;
import com.soup.common.module.sys.entity.Menu;
import com.soup.common.module.sys.entity.Role;
import com.soup.common.module.sys.entity.User;
import com.soup.common.module.sys.mapper.MenuMapper;
import com.soup.common.module.sys.mapper.RoleMenuMapper;
import com.soup.common.module.sys.service.MenuService;
import com.soup.common.module.sys.service.RoleService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 菜单服务方法实现
 *
 * @author zhaoyi
 * @date 2019-08-05 23:04
 * @since 1.0
 */
@Service
@Slf4j
public class MenuServiceImpl extends BaseServiceImpl<Menu> implements MenuService {

    @Resource
    private MenuMapper menuMapper;

    @Resource
    private RoleMenuMapper roleMenuMapper;

    @Autowired
    private RoleService roleService;

    @Autowired
    private CacheService cacheService;

    @Override
    public List<MenuResponse> getLoginUserMenus() {
        User loginUser = LoginContext.LOGIN_USER.get();
        return getRoleMenuPrivilege(loginUser.getRoleId(), false);
    }

    @Override
    public MenuTreeNode getPrivilegeTree(Integer roleId) {
        // 虚拟机根节点，非实际菜单
        MenuTreeNode rootNode = new MenuTreeNode(CommonConstant.NUM_0, null, "权限菜单");
        Role role = roleService.queryRole(roleId);
        List<Integer> roleMenus = Lists.newArrayList();
        if (null != role) {
            roleMenus = role.getMenuIds();
        }
        rootNode.setChildren(this.getChildNode(rootNode.getParentId(), roleMenus));
        return rootNode;
    }

    @Override
    public Map<String, List<MenuTreeNode>> fetchRoleMenuTree(Integer roleId) {
        Map<String, List<MenuTreeNode>> menuMap = Maps.newHashMap();
        // 查询菜单树
        List<MenuTreeNode> menuTree = this.fetchMenus(CommonConstant.NUM_0);
        // 查询角色含有的菜单树
        List<MenuTreeNode> roleTree = this.fetchRoleMenu(roleId);

        menuMap.put("menuTree", menuTree);
        menuMap.put("roleTree", roleTree);
        return menuMap;
    }

    private List<MenuTreeNode> fetchRoleMenu(Integer roleId) {
        List<MenuTreeNode> roleTreeMenus = Lists.newArrayList();

        List<Menu> roleMenus = roleMenuMapper.selectRoleMenuByRoleId(roleId);
        if (CollectionUtils.isNotEmpty(roleMenus)) {
            // 遍历角色子级菜单
            roleMenus.forEach(menu -> {
                // 构建菜单树节点
                MenuTreeNode treeNode = new MenuTreeNode(menu.getId(), menu.getParentId(), menu.getName());
                roleTreeMenus.add(treeNode);
            });
        }

        return roleTreeMenus;
    }

    private List<MenuTreeNode> fetchMenus(Integer parentMneuId) {
        List<MenuTreeNode> roleTreeMenus = Lists.newArrayList();

        List<Menu> roleMenus = queryChileMenus(parentMneuId);
        if (CollectionUtils.isNotEmpty(roleMenus)) {
            // 遍历子级菜单
            roleMenus.forEach(menu -> {
                // 构建菜单树节点
                MenuTreeNode childNode = new MenuTreeNode(menu.getId(), parentMneuId, menu.getName());
                childNode.setChildren(this.fetchMenus(menu.getId()));
                roleTreeMenus.add(childNode);
            });
        }

        return roleTreeMenus;
    }

    @Override
    public int setUpRolePrivilege(Integer roleId, List<Integer> menuIds) {
        // 先删除原有的关联，在新增新的功能，如果传递空菜单列表，表示清除菜单权限
        roleMenuMapper.deleteByRole(roleId);
        int number = CommonConstant.NUM_0;
        if (CollectionUtils.isNotEmpty(menuIds)) {
            number = roleMenuMapper.addRoleMenus(roleId, menuIds);
        }

        // 角色分配完菜单后，需要删除菜单缓存，重新查询角色菜单信息并进行缓存
        getRoleMenuPrivilege(roleId, true);

        return number;
    }

    @Override
    public int add(MenuRequest menuRequest) {
        // 校验是否已经添加
        int count = countByEqual(ImmutableMap.of("name", menuRequest.getName()));
        if (count > CommonConstant.NUM_0) {
            throw new MenuOperationException(ErrorCode.MENU_ADD_DUPLICATED);
        }

        // 父级菜单和菜单类型校验
        validMenuParentAndType(menuRequest);

        // 计算菜单层级
        int level = CommonConstant.NUM_1;
        if (CommonConstant.NUM_0 != menuRequest.getParentId()) {
            // 根据父级菜单id计算菜单的层级
            level = fetchMenuLevel(menuRequest.getParentId());
        }

        Menu menu = new Menu();
        BeanUtils.copyProperties(menuRequest, menu);
        menu.setMenuLevel(level);
        menu.setGmtCreate(new Date());

        return save(menu);
    }

    @Override
    public int update(Integer menuId, MenuRequest menuRequest) {
        Menu menu = selectByKey(menuId);
        if (null == menu) {
            throw new MenuOperationException(ErrorCode.MENU_NOT_FOUND);
        }
        // 菜单名称是否存在
        QueryParam queryParam = new QueryParam(Menu.class);
        QueryParam.Criteria criteria = queryParam.createCriteria();
        criteria.andEqualTo("name", menuRequest.getName());
        criteria.andNotEqualTo("id", menuId);
        List<Menu> employees = selectByQueryParam(queryParam);
        if (CollectionUtils.isNotEmpty(employees)) {
            throw new MenuOperationException(ErrorCode.MENU_ADD_DUPLICATED);
        }

        // 父级菜单和菜单类型校验
        validMenuParentAndType(menuRequest);

        // 是否修改了菜单层级
        int level = CommonConstant.NUM_1;
        if (menu.getParentId() != menuRequest.getParentId()) {
            // 计算菜单level
            if (CommonConstant.NUM_0 != menuRequest.getParentId()) {
                // 根据父级菜单id计算菜单的层级
                level = fetchMenuLevel(menuRequest.getParentId());
            }
        }

        BeanUtils.copyProperties(menuRequest, menu);
        menu.setMenuLevel(level);
        int number = updateNotNull(menu);

        // 设置权限菜单的缓存失效
        roleService.selectAll().forEach(role -> cacheService.remove(CommonConstant.ROLE_MENUS, role.getId()));

        return number;
    }

    /**
     * 校验添加/修改的菜单的父级和菜单类型是否正确
     *
     * @param menuRequest 添加/修改的菜单的信息
     */
    private void validMenuParentAndType(MenuRequest menuRequest) {
        // 非顶级菜单，需要判断父级菜单是否存在
        if (CommonConstant.NUM_0 != menuRequest.getParentId()) {
            Menu parentMenu = selectByKey(menuRequest.getParentId());
            if (null == parentMenu) {
                throw new MenuOperationException(ErrorCode.MENU_PARENT_NOT_FOUND);
            }

            // 如果添加的是方法菜单上级必须是分组菜单或控制器菜单
            if (CommonConstant.METHOD_MENU == menuRequest.getMenuType()) {
                if (parentMenu.getMenuType() != CommonConstant.GROUP_MENU
                        && parentMenu.getMenuType() != CommonConstant.CONTROLLER_MENU) {
                    throw new MenuOperationException(ErrorCode.MENU_PARENT_NOT_CORRECT);
                }
            }
        } else {
            // 顶级菜单不可以方法菜单或控制器菜单
            if (CommonConstant.GROUP_MENU != menuRequest.getMenuType()) {
                throw new MenuOperationException(ErrorCode.MENU_TYPE_NOT_CORRECT);
            }
        }
    }

    @Override
    public List<Menu> allMenus(String searchKeywords) {
        // 按菜单层级查询顶级，然后遍历查询子集，父子级不层级存放，都放在一个list中
        return getChildMenu(CommonConstant.NUM_0);
    }

    @Override
    public int del(Integer menuId) {
        Menu menu = selectByKey(menuId);
        if (null == menu) {
            throw new MenuOperationException(ErrorCode.MENU_NOT_FOUND);
        }

        // 如果菜单有下级菜单，不可以删除
        int countChild = countByEqual(ImmutableMap.of("parentId", menu.getId().toString()));
        if (countChild > CommonConstant.NUM_0) {
            throw new MenuOperationException(ErrorCode.MENU_HAS_CHILD);
        }

        int number = delete(menuId);
        if (number > CommonConstant.NUM_0) {
            // 菜单发生变动，设置权限菜单的缓存失效
            roleService.selectAll().forEach(role -> cacheService.remove(CommonConstant.ROLE_MENUS, role.getId()));
        }

        return number;
    }

    private int fetchMenuLevel(int parentId) {
        int level = CommonConstant.NUM_1;
        Menu tmpMenu = selectByKey(parentId);
        if (null != tmpMenu && CommonConstant.NUM_0 != tmpMenu.getParentId()) {
            level = fetchMenuLevel(tmpMenu.getParentId());
            level++;
        }

        return level;
    }

    /**
     * 获取角色的菜单信息
     *
     * @param roleId 角色id
     * @param refreshCache 是否刷新角色的缓存菜单，<tt>true</tt>刷新缓存
     * @return 角色菜单列表
     */
    private List<MenuResponse> getRoleMenuPrivilege(Integer roleId, boolean refreshCache) {
        List<MenuResponse> roleMemus;
        String roleMenuFuncJson = CommonConstant.BLANK;
        if (!refreshCache) {
            // 不刷新缓存菜单，表示先从缓存中获取缓存菜单
            roleMenuFuncJson = cacheService.get(CommonConstant.ROLE_MENUS, roleId);
        }

        if (StringUtils.isEmpty(roleMenuFuncJson)) {
            log.debug("role id [{}] menus cache was gone, requery from db", roleId);
            // 查询角色菜单，如果是默认角色，则不需要传递角色id
            if (CommonConstant.NUM_1 == roleId) {
                roleMemus = buidRoleMenu(CommonConstant.NUM_0, null);
            } else {
                roleMemus = buidRoleMenu(CommonConstant.NUM_0, roleId);
            }

            // 将查询的角色菜单缓存
            if (CollectionUtils.isNotEmpty(roleMemus)) {
                cacheService.set(CommonConstant.ROLE_MENUS, roleId, JSON.toJSONString(roleMemus));
            }
        } else {
            log.debug("role id [{}] menus cache was hited", roleId);
            roleMemus = JSON.parseObject(roleMenuFuncJson, new TypeReference<List<MenuResponse>>() {}, Feature.OrderedField);
        }

        return roleMemus;
    }

    /**
     * 查询子级别菜单
     *
     * @param parentId 父级菜单id，顶级菜单的父级菜单为0
     * @return 菜单列表
     */
    private List<Menu> getChildMenu(Integer parentId) {
        List<Menu> menus = Lists.newArrayList();

        List<Menu> childMenus = queryChileMenus(parentId);
        if (CollectionUtils.isNotEmpty(childMenus)) {
            childMenus.forEach(childMenu -> {
                menus.add(childMenu);

                // 查询子集
                List<Menu> children = this.getChildMenu(childMenu.getId());
                if (CollectionUtils.isNotEmpty(children)) {
                    menus.addAll(children);
                }
            });
        }

        return menus;
    }

    /**
     * 查询子级菜单
     *
     * @param parentMenuId 父级菜单
     * @return 菜单列表
     */
    private List<Menu> queryChileMenus(Integer parentMenuId) {
        QueryParam queryParam = new QueryParam(Menu.class);
        QueryParam.Criteria criteria = queryParam.createCriteria();
        if (null == parentMenuId) {
            criteria.andIsNull("parentId");
        } else {
            criteria.andEqualTo("parentId", parentMenuId);
        }
        // 按照菜单排序值排序
        queryParam.orderBy("sortValue").asc();
        return selectByQueryParam(queryParam);
    }

    /**
     * 递归构建角色菜单列表
     *
     * @param parentMenuId 父级菜单id
     * @param roleId 角色id，如果为空，表示查询的是所有的菜单，不受权限控制
     * @return 菜单列表
     */
    private List<MenuResponse> buidRoleMenu(Integer parentMenuId, Integer roleId) {
        List<MenuResponse> menus = Lists.newArrayList();

        // 查询子级菜单列表，不包括方法菜单
        List<Menu> childMenu;
        if (null != roleId) {
            childMenu = menuMapper.selecctRoleChileMenus(parentMenuId, roleId);
        } else {
            childMenu = menuMapper.selecctChileMenus(parentMenuId);
        }

        if (CollectionUtils.isNotEmpty(childMenu)) {
            childMenu.forEach(menu -> {
                // 分组菜单和控制菜单集合
                MenuResponse menuResponse = new MenuResponse(menu.getId(), menu.getName(), menu.getName(),
                        menu.getIcon(), menu.isSpread(), menu.getUrl());
                // 方法菜单集合，每个非方法菜单下只能挂载一组方法菜单
                List<MethodMenuResponse> methodMenus;
                if (null != roleId) {
                    methodMenus = menuMapper.selectRoleMethodMenus(menu.getId(), roleId);
                } else {
                    methodMenus = menuMapper.selectMethodMenus(menu.getId());
                }
                menuResponse.setMethodMenus(methodMenus);
                menuResponse.setList(this.buidRoleMenu(menu.getId(), roleId));
                menus.add(menuResponse);
            });
        }

        return menus;
    }

    /**
     * 构建子级菜单树
     *
     * @param parentId 父级菜单id
     * @param roleMenus 角色菜单id列表
     * @return 菜单树列表
     */
    private List<MenuTreeNode> getChildNode(Integer parentId, List<Integer> roleMenus) {
        List<MenuTreeNode> menuTreeNodes = Lists.newArrayList();

        // 查询子级菜单列表
        List<Menu> childMenu = queryChileMenus(parentId);
        if (CollectionUtils.isNotEmpty(childMenu)) {
            // 遍历子级菜单，判断是否在角色菜单列表中
            childMenu.forEach(menu -> {
                // 构建菜单树节点
                MenuTreeNode childNode = new MenuTreeNode(menu.getId(), parentId, menu.getName());
                // 角色菜单中包含此菜单，设置勾选
                if (roleMenus.contains(menu.getId())) {
                    childNode.setChecked(true);
                }
                childNode.setChildren(this.getChildNode(menu.getId(), roleMenus));
                menuTreeNodes.add(childNode);
            });
        }

        return menuTreeNodes;
    }
}
